//
//		BILBAO
//
//      @name Thread.h
//      @author Bematech S.A.
//

#include "bilbao/lang/Thread.h"

namespace bilbao
{
	namespace lang
	{
		// --------------------------------------------------------------------
		BUINT32 Thread::PRIORITY_MAX = 100;
		// --------------------------------------------------------------------
		BUINT32 Thread::PRIORITY_MIN = 0;
		// --------------------------------------------------------------------
		BUINT32 Thread::PRIORITY_NORMAL = 50;
		// --------------------------------------------------------------------
		Thread::Thread(void)
			: running(false), runnable(NULL), priority(Thread::PRIORITY_NORMAL), created(false)
		{
		}
		// --------------------------------------------------------------------
		Thread::Thread( Runnable* runner )
			: running(false), runnable(runner), priority(Thread::PRIORITY_NORMAL), created(false)
		{
		}
		// --------------------------------------------------------------------
		Thread::~Thread(void)
		{
		    if( running )
		    {
				stop ();
		    }

		#if BILBAO_PLATAFORM == BILBAO_WIN32
			CloseHandle (thread);
			thread = 0;
		#endif
		}
		// --------------------------------------------------------------------
		void Thread::setPriority( BUINT32 pr )
		{
			priority = pr;
		}
		// --------------------------------------------------------------------
		BUINT32 Thread::getPriority(void) const
		{
			return priority;
		}
		// --------------------------------------------------------------------
        void Thread::start(void)
		{
			if( running )
			{
				//TODO: LOG!
				//LOG("Thread is already running");
				return;
			}

		#if BILBAO_PLATAFORM == BILBAO_WIN32

			// creates the thread
			thread = CreateThread (0, 0, (LPTHREAD_START_ROUTINE) threadRun, (void*)this, CREATE_SUSPENDED, 0);

			// OR use the MSVCRT option
			//muint dummyThreadId;
			//thread = (HANDLE)_beginthreadex (0, 0, threadRun, (void*)this,CREATE_SUSPENDED, &dummyThreadId);

			// computes the priority
			int winPriority = THREAD_PRIORITY_NORMAL;
			if (priority <= 20)
				winPriority = THREAD_PRIORITY_LOWEST;
			else if (priority <= 40)
				winPriority = THREAD_PRIORITY_BELOW_NORMAL;
			else if (priority <= 60)
				winPriority = THREAD_PRIORITY_NORMAL;
			else if (priority <= 80)
				winPriority = THREAD_PRIORITY_ABOVE_NORMAL;
			else if (priority <= 100)
				winPriority = THREAD_PRIORITY_HIGHEST;

			// explicitly sets the priority and resumes the thread
			SetThreadPriority( thread, winPriority);
			running = ( ResumeThread(thread) != (DWORD)-1 );
		#else
			// pthread version
			if (created)
			{
				pthread_join (thread, 0); // clean up resources
				created = false;
				return;
			}

			pthread_attr_t attribs;
			int rc;

			pthread_attr_init(&attribs);
			pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_JOINABLE );
			rc = pthread_create(&thread, &attribs, threadRun, (void*)this);

			if( rc )
			{
				// TODO: check errors
				/* EAGAIN: "Out of system resources"
					EINVAL: "Tried to create thread with wrong attributes"
					EPERM:  "No permission to create thread"
				*/
			}
			else
			{
				running = true;
				created = true;

				// sets priority:
				struct sched_param sched;
				memset(&sched, 0, sizeof(struct sched_param));
				//sched.sched_priority = priority;

				sched.sched_priority = sched_get_priority_max(SCHED_OTHER);
				if ( pthread_setschedparam( thread, SCHED_OTHER, &sched) != 0 )
				{
					//TODO: LOG!
					//LOG("Failed to set thread priority %d.");
				}
			}

			//
			pthread_attr_destroy(&attribs);
		#endif
		}
		// --------------------------------------------------------------------
        void Thread::stop(void)
		{
			if( running )
			{
			#if BILBAO_PLATAFORM == BILBAO_WIN32
				running = !TerminateThread (thread, ~0);
			#else
				// pthread version
				if( !pthread_cancel (thread) )
				{
					running = false;
				}
			#endif
			}
		}
		// --------------------------------------------------------------------
		void Thread::sleep( BULONG milliseconds )
		{
		#if BILBAO_PLATAFORM == BILBAO_WIN32
			Sleep(milliseconds);
		#else
			BULONG microseconds = milliseconds*1000;
			#if 1 /*defined(HAVE_NANOSLEEP)*/
				timespec tmReq;
				tmReq.tv_sec = (time_t)(microseconds / 1000000);
				tmReq.tv_nsec = (microseconds % 1000000) * 1000;

				// we're not interested in remaining time nor in return value
				(void)nanosleep(&tmReq, (timespec *)NULL);
			#elif defined(HAVE_USLEEP)
				usleep(microseconds);

			#elif defined(HAVE_SLEEP)
				// sleep does it in seconds
				::sleep(microseconds * 1000000);
			#else
				::sleep( microseconds );

			#endif
		#endif
		}
		// --------------------------------------------------------------------
		void Thread::wait(void)
		{
		#if BILBAO_PLATAFORM == BILBAO_WIN32
			if (running)
			{
				if( WaitForSingleObject (thread, INFINITE) == WAIT_FAILED )
				{
					// TODO: find error and log it
				}
			}
		#else
			if (running)
			{
				int rc = pthread_join (thread,0);
				if( !rc )
				{
				  running = false;
				  created = false;
				}
			}
		#endif
		}
		// --------------------------------------------------------------------
		void Thread::yield(void)
		{
		#if BILBAO_PLATAFORM == BILBAO_WIN32
			if (running)
				Sleep(0);
		#else
			if (running) sched_yield();
		#endif
		}
		//
		int Thread::join(void)
		{
			return pthread_join( thread, 0 );
		}
		// --------------------------------------------------------------------
		bool Thread::isRunning(void) const
		{
			return running;
		}
		// --------------------------------------------------------------------
		void Thread::run(void)
		{
			// only runs if runnable is set
			if( runnable ) runnable->run();
		}
		// --------------------------------------------------------------------
		#if BILBAO_PLATAFORM == BILBAO_WIN32
		UINT __stdcall Thread::threadRun(void* param)
		{
			Thread* thread = (Thread*)param;
				// runs the thread
				thread->run();
				thread->running = false;
			ExitThread (0); // CRT exit

			// mscrt version
			//_endthreadex (0); // msvcrt version
			return 0;
		}
		#else
		void* Thread::threadRun (void* param)
		{
			Thread* thread = (Thread*)param;
			thread->run();

			thread->running = false;
			pthread_exit (0);
			return 0;
		}
		#endif
		// --------------------------------------------------------------------
	}
}

